iT邦幫忙

2021 iThome 鐵人賽

DAY 21
1
Modern Web

連線網頁卡牌遊戲(Elixir, Phoenix, Liveview)系列 第 21

21 "準備完成" 用 PubSub 同步更新網頁

  • 分享至 

  • xImage
  •  

拉出 component

Component 除了在同一個 module 用之外也能拉出來放
我們來把 logo 拉出來到 lib/card_web/component.ex

defmodule CardWeb.Component do
  use Phoenix.Component

  def logo(assigns) do
    ~H"""
    <header class="text-blue-500 m-4">
      <a href="/" class="text-5xl font-serif">
        ? CardyTotala
      </a>
    </header>
    """
  end
end

我要用到的時候我可以用

<CardWeb.Component.logo />

如果我有在要用的地方 import CardWeb.Component 的話,
我就可以直接使用

<.logo />

我感覺等待畫面需要有一個大家有沒有準備好的狀態顯示

  def ready(assigns) do
    ~H"""
    <div class="flex flex-col p-4 text-center mt-8">
      <div class="bg-blue-300 w-32 h-8 rounded-xl transform skew-x-12 -rotate-6 translate-y-6 translate-x-20"></div>
      <h2 class="transform text-2xl font-serif">Room status</h2>
      <div class="flex justify-between mt-2 w-60">
        <.ready_status player="Host" ready={@room.host_ready}/>
        <.ready_status player="Guest" ready={@room.guest_ready}/>
      </div>
      <%= unless Map.get(@room, :"#{@player}_ready") do %>
        <div class="flex justify-between w-40 mx-auto mt-8">
          <div class="text-lg text-center">Are you ready?</div>
          <div class="h-8">
            <div class="bg-green-300 w-8 h-8 rounded-xl transform skew-x-12 -rotate-45 translate-x-2"></div>
            <button class="transform -translate-y-7 text-xl">Yes</button>
          </div>
        </div>
      <% end %>
    </div>
    """
  end

  def ready_status(assigns) do
    ~H"""
    <div class="flex">
      <%= @player %>:
      <%= if @ready do %>
        <div class={"ml-8 bg-green-300 w-6 h-6 rounded-3xl transform -skew-x-3 rotate-12"}></div>
      <% else %>
        <div class={"ml-8 bg-red-300 w-6 h-6 rounded-3xl transform -skew-x-3 rotate-12"}></div>
      <% end %>
    </div>
    """
  end

畫面在 host 與 guest 的狀態
https://ithelp.ithome.com.tw/upload/images/20211004/20141054e5jlM8NP8T.png

PubSub

在玩家按下 準備好 按鈕的時候,這次不只要更新畫面上的狀態,還要跟對手同步

要做的事情有

  1. 接收按鈕準備好按鈕
  2. 儲存更新後的數值
  3. 廣播更新後的數值
  4. 接收廣播

感覺要包一些進 room.ex

defmodule Card.Room do
  defstruct id: nil, game: nil, game_pid: nil, host_ready: false, guest_ready: false

  # 訂閱某個 room_id 頻道
  def subscribe(id) do
    Phoenix.PubSub.subscribe(Card.PubSub, id)
  end

  # 廣播 room 物件至某個 room_id 
  def broadcast(id, room) do
    Phoenix.PubSub.broadcast(Card.PubSub, id, room)
  end

  # 從 ETS 表裡面用 room_id 找 room ,沒找到就回 nil
  def get(id) do
    case :ets.lookup(:rooms, id) do
      [{_, room}] -> room
      _ -> nil
    end
  end

  # 在 ETS 表上更新指定 room ,順便廣播
  def update(id, room) do
    :ets.insert(:rooms, {id, room})
    broadcast(room.id, room)
  end
end

這樣子 mount 那邊要拿 room 的時後也要記得改用 get

把上面那些方法寫好應該就很快了

  1. 接收按鈕準備好按鈕
    加上 phx-click
  <button phx-click="ready" class="transform -translate-y-7 text-xl">Yes</button>

接收 ready 事件

  def handle_event("ready", _params, %{assigns: %{player: player, room: room}} = socket) do
    room =
      socket.assigns.room
      |> Map.replace(:"#{player}_ready", true)

# 2. 儲存更新後的數值
    Room.update(room.id, room)

    {:noreply, socket}
  end
  1. 廣播更新後的數值
    寫在update裡了
  2. 接收廣播

收到新的 room 就更新就好了

  def handle_info(room, socket) do
    {:noreply, assign(socket, :room, room)}
  end

狀態改了就好了

LiveView 這樣統一狀態超讚的,畫面自己就會跟著變了
動圖:
https://ppt.cc/f7bEwx@.gif


上一篇
20 等待對手頁面
下一篇
22 準備完成後跳轉到遊戲頁面
系列文
連線網頁卡牌遊戲(Elixir, Phoenix, Liveview)32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言